package com.jx.driving.protocol.terminal.codec;

import com.jx.driving.message.*;
import com.jx.driving.protocol.terminal.channel.Channel;
import com.jx.driving.request.Terminal0x0900Request;
import com.jx.driving.request.Terminal0x8003Request;
import com.jx.driving.request.domain.DynamicObject;
import com.jx.driving.request.domain.Word;
import com.jx.driving.response.AbstractResponse;
import com.jx.driving.response.ResponseResultStatus;
import com.jx.driving.response.Terminal0x0001Response;
import com.jx.driving.response.Terminal0x8001Response;
import com.jx.driving.transform.DefaultTransform;
import com.jx.driving.transform.Transform;
import com.jx.driving.transmission.Terminal0x0202Transmission;
import com.jx.driving.transmission.Terminal0x0305Transmission;
import com.jx.driving.util.ChannelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 终端消息体解析 TODO解决服务端与客户端不同消息响应问题
 *
 * @author shenliuyang
 * <p>
 * 2016年8月4日下午3:19:20
 */
public class TerminalBodyCodec implements Codec {

    private static final Logger logger = LoggerFactory.getLogger(TerminalBodyCodec.class);

    private boolean client;
    // 依赖消息转换
    private Transform transform;
    private AtomicInteger atomicInteger = new AtomicInteger();

    public TerminalBodyCodec(boolean client) {
        this(new DefaultTransform());
        this.client = client;
    }

    public TerminalBodyCodec() {
        this(new DefaultTransform());
    }

    public TerminalBodyCodec(Transform transform) {
        super();
        this.transform = transform;
    }

    @Override
    public Object decode(Channel c, Object in) {
        if (!(in instanceof Message)) {
            return in;
        }
        Message message = (Message) in;
        Object realMessage = processMessage(c, message);
        if (realMessage == null) {
            return null;
        }

        if (!(realMessage instanceof AbstractResponse)) {
            return realMessage;
        }
        AbstractResponse res = (AbstractResponse) realMessage;
        int msgId = res.getId();
        Map<Integer, List<Message>> frameListMap = getCallableMap(c, msgId);
        List<Message> frameList = frameListMap.get(msgId);
        if (frameList == null) {
            return realMessage;
        }
        if (res.getResult() != ResponseResultStatus.SUCCESS.ordinal()) {
            logger.warn("client do not support server message id  {}", toRealMessageId(msgId));
            return null;
        }
        return null;
    }

    @Override
    public Object encode(Channel c, Object in) {
        //处理消息转发
        if (in instanceof TimeTerminalRequestMessage) {
            TimeTerminalRequestMessage req = (TimeTerminalRequestMessage) in;
            List<Message> message = req.getOriginal();
            if (message != null && !message.isEmpty()) {
                logger.debug("encode request message , original message was found. send it directly frame is {}",
                        message.size());
                return message;
            }
        }
        Integer messageId = MessageIdMapper.instance.getId(in.getClass());
        if (messageId == null) {
            return in;
        }
        String phone = ChannelUtils.getPhone(c);
        Integer sequence = getAndAdd(c, messageId, 1);
        byte[] body = transform.transformObjectToByteArray(in);
        List<Message> messageList = Message.getMessageList(body, messageId, sequence, phone);
        if (messageList.size() > 1) {
            getAndAdd(c, messageId, messageList.size() - 1);
        }
        if (in instanceof TimeTerminalRequestMessage) {
            TimeTerminalRequestMessage req = (TimeTerminalRequestMessage) in;
            req.setMsgId(messageId);
            req.setSeq(sequence + messageList.size() - 1);
        }
        return messageList;
    }

    public Object encodeFor0202(Terminal0x0202Transmission msg, String phone, String deviceNo) {

        TransmissionMessageIdMapper idMapper = new TransmissionMessageIdMapper();
        Terminal0x0900Request req = new Terminal0x0900Request();
        DynamicObject dynamicObject = new DynamicObject();
        int id = idMapper.getId(msg.getClass());
        byte[] content = transform.transformObjectToByteArray(msg);
        dynamicObject.setId(id);
        dynamicObject.setExt(1);
        dynamicObject.setSerialNo(0);
        dynamicObject.setDeviceNO(deviceNo);
        dynamicObject.setContent(content);
        req.setObject(dynamicObject);
        Integer messageId = MessageIdMapper.instance.getId(req.getClass());
        if (messageId == null) {
            return req;
        }
        byte[] body = transform.transformObjectToByteArray(req);
        List<Message> messageList = Message.getMessageList(body, messageId, atomicInteger.getAndIncrement(), phone);
        return messageList;
    }

    static final int initPhotoMessageId = TransmissionMessageIdMapper.instance.getId(Terminal0x0305Transmission.class);

    private Object processMessage(Channel channel, Message msg) {
        MessageHeader header = msg.getMessageHeader();
        ChannelUtils.setPhone(channel, header.getPhone());
        MessageBodyProperty body = header.getMessageBodyProperty();
        Integer messageId = header.getMessageId();
        // 处理不支持的消息
        if (MessageIdMapper.instance.getClass(messageId) == null) {
            if (client) {
                channel.tell(new Terminal0x0001Response(header.getSeq(), messageId, ResponseResultStatus.NOSUPPORT));
            } else {
                channel.tell(new Terminal0x8001Response(header.getSeq(), messageId, ResponseResultStatus.NOSUPPORT));
            }

            return null;
        }
        // 处理分包消息
        if (body.getSubPackageFlag()) {
            return messageFrame(channel, msg);
        }
        // 处理不分包的消息
        MessageIdMapper mapper = MessageIdMapper.instance;
        Object realMessage = transform.transformToObject(new ByteArrayInputStream(msg.getMessageBody()), mapper.getClass(messageId));
        if (realMessage instanceof Terminal0x0900Request && ((Terminal0x0900Request) realMessage).dynamicObject().getId() == initPhotoMessageId) {
            //干掉照片数据包缓存准备新的照片上传
            logger.debug("receive 0x0305 message  prepare to clean photo cache");
            Integer photoContentMessageId = MessageIdMapper.instance.getId(Terminal0x0900Request.class);
            Map<Integer, List<Message>> sequenceMessageMap = messageFrameMap(channel, photoContentMessageId);
            List<Message> frameList = sequenceMessageMap.get(photoContentMessageId);
            if (frameList != null && (!frameList.isEmpty())) {
                logger.debug("photo cache was cleared.");
                frameList.clear();
            }
        }
        return decodedPostProcess(realMessage, Arrays.asList(msg));
    }

    /**
     * 处理分包消息
     */
    private Object messageFrame(Channel channel, Message msg) {
        MessageHeader head = msg.getMessageHeader();
        Integer messageId = head.getMessageId();
        Integer sequence = head.getSeq();
        Map<Integer, List<Message>> sequenceMessageMap = messageFrameMap(channel, messageId);
        List<Message> frameList = sequenceMessageMap.get(messageId);
        if (frameList == null) {
            frameList = new ArrayList<>();
            sequenceMessageMap.put(messageId, frameList);
        }
        logger.debug("package number {}   package sum {} sequence {}", head.getPackageNumber(), head.getPackageSum(),
                sequence);
        for (Message f : frameList) {
            Integer packageNumber = f.getMessageHeader().getPackageNumber();
            if (Objects.equals(packageNumber, head.getPackageNumber())) {
                if (Objects.equals(f.getMessageHeader().getSeq(), head.getSeq())) {
                    logger.warn("client package repeated {}", packageNumber);
                    frameList.remove(f);
                } else {
                    logger.warn("client start a new  package {}", packageNumber);
                    frameList.clear();
                }
                break;
            }
        }
        frameList.add(msg);
        // 排序包
        Collections.sort(frameList, new Comparator<Message>() {
            @Override
            public int compare(Message o1, Message o2) {
                return o1.getMessageHeader().getPackageNumber().compareTo(o2.getMessageHeader().getPackageNumber());
            }
        });
        if (client) {
            channel.tell(new Terminal0x0001Response(sequence, messageId, ResponseResultStatus.SUCCESS));
        } else {
            channel.tell(new Terminal0x8001Response(sequence, messageId, ResponseResultStatus.SUCCESS));
        }
        // 如果分包数据没有结束.或者是补传发送
        if (head.getPackageNumber() != head.getPackageSum()
                && frameList.get(frameList.size() - 1).getMessageHeader().getPackageNumber() != head.getPackageSum()) {
            return null;
        }
        // 消息发送结束了.进行解码操作

        return combineFrameAndDecodeMessage(channel, messageId, frameList, head.getPackageSum(), sequenceMessageMap);
    }

    /**
     * 组合分包并解码消息
     */
    private Object combineFrameAndDecodeMessage(Channel channel, Integer messageId, List<Message> frameList,
                                                Integer totalPackage, Map<Integer, List<Message>> sequenceMessageMap) {
        // 检测消息分包数是否正确
        if (frameList.size() != totalPackage) {
            logger.debug("combineFrameAndDecodeMessage frameList size is {} , totalPackage is {}", frameList.size(),
                    totalPackage);
            List<Word> idList = new ArrayList<>();
            for (int i = 1; i <= totalPackage; i++) {
                boolean exist = false;
                for (Message msg : frameList) {
                    if (msg.getMessageHeader().getPackageNumber() == i) {
                        exist = true;
                    }
                }
                if (!exist) {
                    idList.add(new Word(i));
                }
            }
            //最后一包
            MessageHeader last = frameList.get(frameList.size() - 1).getMessageHeader();
            int lastSequence = last.getSeq();
            //计算出第一包序列号(存在第一包丢失可能)
            int firstSequence = lastSequence - (totalPackage - 1);
            Terminal0x8003Request req = new Terminal0x8003Request();
            req.setOriginalSeq(firstSequence);
            req.setIdList(idList);
            logger.debug("client package not fully. tell client retry again {}", req);
            channel.tell(req);
            return null;
        }
        sequenceMessageMap.remove(messageId);

        ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
        for (int i = 0; i < totalPackage; i++) {
            Message message = frameList.get(i);
            if (message.getMessageHeader().getPackageNumber() - 1 != i) {
                logger.error("message number not match: packageNumber {} message: {}", totalPackage, message);
                return null;
            }
            try {
                out.write(message.getMessageBody());
            } catch (IOException e) {
                // never happend
            }
        }
        MessageIdMapper mapper = MessageIdMapper.instance;
        byte[] content = out.toByteArray();
        return decodedPostProcess(
                transform.transformToObject(new ByteArrayInputStream(content), mapper.getClass(messageId)), frameList);
    }

    /**
     * 获取消息分包队列数据
     */
    private Map<Integer, List<Message>> messageFrameMap(Channel channel, Integer id) {
        Map<Integer, Map<Integer, List<Message>>> attribute = channel.attr("packageFrame");
        if (attribute == null) {
            attribute = new HashMap<>();
            channel.attr("packageFrame", attribute);
        }
        // 消息类型对应流水号与消息的绑定
        Map<Integer, List<Message>> messageSequenceMap = attribute.get(id);
        if (messageSequenceMap == null) {
            messageSequenceMap = new HashMap<>();
            attribute.put(id, messageSequenceMap);
        }
        return messageSequenceMap;
    }

    /**
     * 获取客户端回调
     */
    private Map<Integer, List<Message>> getCallableMap(Channel channel, int messageId) {
        Map<Integer, Map<Integer, List<Message>>> result = channel.attr("CALLBALE_KEY");
        if (result == null) {
            result = new HashMap<>();
            channel.attr("CALLBALE_KEY", result);
        }
        Map<Integer, List<Message>> res = result.get(messageId);
        if (res != null) {
            return res;
        }
        res = new HashMap<>();
        result.put(messageId, res);
        return res;
    }

    /**
     * 获取消息序列
     */
    private Integer getAndAdd(Channel channel, Integer messageId, int num) {
        Map<Integer, Integer> messageSequence = channel.attr("MESSAGE_SEQUENCE_KEY");
        Map<Integer, Integer> messageSequenceMap = messageSequence;
        if (messageSequenceMap == null) {
            messageSequenceMap = new HashMap<>();
            channel.attr("MESSAGE_SEQUENCE_KEY", messageSequenceMap);
        }
        Integer sequence = messageSequenceMap.get(messageId);
        if (sequence == null) {
            sequence = 0;
        }
        messageSequenceMap.put(messageId, sequence + num);
        return sequence;
    }

    private Object decodedPostProcess(Object realMessage, List<Message> msg) {
        Message last = msg.get(msg.size() - 1);
        if (realMessage instanceof TimeTerminalRequestMessage) {
            TimeTerminalRequestMessage req = (TimeTerminalRequestMessage) realMessage;
            req.setSeq(last.getMessageHeader().getSeq());
            req.setMsgId(last.getMessageHeader().getMessageId());
            req.setOrginalMessage(msg);
        }
        return realMessage;
    }

    public void setTransform(Transform transform) {
        this.transform = transform;
    }

    public String toRealMessageId(int id) {
        return Integer.toHexString(id);
    }

}
